unit TestCoreEntityMap;

interface

uses
  Rtti,
  TestFramework,
  Spring.TestUtils,
  Spring.Collections,
  Spring.Persistence.Core.EntityMap,
  Spring.Persistence.Mapping.Attributes,
  TestEntities;

type
  TMockEntityMap = class(TEntityMap);

  {$HINTS OFF}
  TEntityMapTest = class(TTestCase)
  private
    FEntityMap: TMockEntityMap;
  public
    procedure SetUp; override;
    procedure TearDown; override;
  published
    procedure TestIsMapped;
    procedure TestAdd;
    procedure TestAddOrReplace;
    {$IFDEF PERFORMANCE_TESTS}
    procedure TestAddOrReplace_Clone_Speed;
    {$ENDIF}
    procedure TestRemove;
    procedure TestClear;
    procedure TestHash;
    procedure When_Changed_One_Property_GetChangedMembers_Returns_It;
    procedure When_Replaced_Entity_No_MemoryLeaks;
  end;

  [Table]
  TTest1 = class
  public
    [Column('TESTID', [cpRequired, cpPrimaryKey, cpNotNull], 0, 0, 0, 'Primary Key')]
    [AutoGenerated]
    FId: Integer;
  end;

  [Table]
  TTest2 = class
  public
    [Column('TESTID', [cpRequired, cpPrimaryKey, cpNotNull], 0, 0, 0, 'Primary Key')]
    [AutoGenerated]
    FId: Integer;
  end;

implementation

uses
  Diagnostics,
  Generics.Collections,
  SysUtils,
  Spring.Persistence.Core.EntityCache;

function CreateCustomer: TCustomer;
begin
  Result := TCustomer.Create;
  Result.Name := 'Test Case';
  Result.Age := 15;
  Result.Height := 1.11;
  Result.LastEdited := EncodeDate(2011,1,1);
  Result.EMail := 'test@gmail.com';
end;

function CreateCompany: TCompany;
begin
  Result := TCompany.Create;
  Result.Name := 'Test Company';
end;

procedure TEntityMapTest.SetUp;
begin
  FEntityMap := TMockEntityMap.Create;
end;

procedure TEntityMapTest.TearDown;
begin
  FEntityMap.Free;
end;

procedure TEntityMapTest.TestIsMapped;
var
  ReturnValue: Boolean;
  AObject, LClone: TCustomer;
  LCompany, LClonedCompany: TCompany;
begin
  AObject := CreateCustomer;
  LCompany := CreateCompany;
  LClone := CreateCustomer;
  LClonedCompany := CreateCompany;
  try
    ReturnValue := FEntityMap.IsMapped(AObject);
    CheckFalse(ReturnValue);
    FEntityMap.AddOrReplace(LClone);
    ReturnValue := FEntityMap.IsMapped(AObject);
    CheckTrue(ReturnValue);

    ReturnValue := FEntityMap.IsMapped(LCompany);
    CheckFalse(ReturnValue);
    FEntityMap.AddOrReplace(LClonedCompany);

    ReturnValue := FEntityMap.IsMapped(LCompany);
    CheckTrue(ReturnValue);
  finally
    AObject.Free;
    LCompany.Free;
    LClone.Free;
    LClonedCompany.Free;
  end;
end;

procedure TEntityMapTest.TestAdd;
var
  LCustomer: TCustomer;
begin
  LCustomer := CreateCustomer;
  try
    CheckFalse(FEntityMap.IsMapped(LCustomer));
    FEntityMap.AddOrReplace(LCustomer);
    CheckTrue(FEntityMap.IsMapped(LCustomer));
  finally
    LCustomer.Free;
  end;
end;

procedure TEntityMapTest.TestAddOrReplace;
var
  LCustomer: TCustomer;
begin
  LCustomer := CreateCustomer;
  try
    CheckFalse(FEntityMap.IsMapped(LCustomer));
    FEntityMap.AddOrReplace(LCustomer);
    CheckTrue(FEntityMap.IsMapped(LCustomer));
    FEntityMap.AddOrReplace(LCustomer);
    CheckTrue(FEntityMap.IsMapped(LCustomer));
  finally
    LCustomer.Free;
  end;
end;

{$IFDEF PERFORMANCE_TESTS}
procedure TEntityMapTest.TestAddOrReplace_Clone_Speed;
var
  iCount: Integer;
  sw: TStopwatch;
  i: Integer;
  LCustomers: IList<TCustomer>;
  LSpringDict: IDictionary<string,TValue>;
  LObjectDict: IDictionary<string,TObject>;
  LNativeDict: TDictionary<string,TValue>;
  LValue: TValue;
begin
  iCount := 50000;
  LCustomers := TCollections.CreateObjectList<TCustomer>(True);
  for i := 1 to iCount do
  begin
    LCustomers.Add(CreateCustomer);
  end;

  sw := TStopwatch.StartNew;
  for i := 0 to iCount - 1 do
    FEntityMap.AddOrReplace(LCustomers[i]);
  sw.Stop;

  Status(Format('%d items in %d ms', [iCount, sw.ElapsedMilliseconds]));

//  //previous implementation
//  LObjectDict := TCollections.CreateDictionary<string, TObject>([doOwnsValues]);
//  sw := TStopwatch.StartNew;
//  for i := 0 to iCount - 1 do
//  begin
//    LObjectDict.AddOrSetValue('some random key', TRttiExplorer.Clone(LCustomers[i]));
//  end;
//  sw.Stop;
//
//  Status(Format('Previous implementation: %d items in %d ms', [iCount, sw.ElapsedMilliseconds]));

  iCount := iCount * 10;
  sw := TStopwatch.StartNew;
  LSpringDict := TCollections.CreateDictionary<string, TValue>;
  for i := 0 to iCount - 1 do
  begin
    LValue := i;
    LSpringDict.AddOrSetValue('some random key', LValue);
  end;
  sw.Stop;
  Status(Format('Spring dictionary %d items in %d ms', [iCount, sw.ElapsedMilliseconds]));
  sw := TStopwatch.StartNew;
  LNativeDict := TDictionary<string,TValue>.Create;
  for i := 0 to iCount - 1 do
  begin
    LValue := i;
    LNativeDict.AddOrSetValue('some random key', LValue);
  end;
  sw.Stop;
  Status(Format('Native dictionary %d items in %d ms', [iCount, sw.ElapsedMilliseconds]));
  LNativeDict.Free;
end;
{$ENDIF}

procedure TEntityMapTest.TestHash;
var
  LTest1: TTest1;
  LTest2: TTest2;
begin
  LTest1 := TTest1.Create;
  LTest2 := TTest2.Create;
  try
    LTest2.FId := 220;

    CheckFalse(FEntityMap.IsMapped(LTest1));
    CheckFalse(FEntityMap.IsMapped(LTest2));
    FEntityMap.AddOrReplace(LTest1);
    CheckTrue(FEntityMap.IsMapped(LTest1));
    CheckFalse(FEntityMap.IsMapped(LTest2));
  finally
    LTest1.Free;
    LTest2.Free;
  end;
end;

procedure TEntityMapTest.TestRemove;
var
  LCustomer: TCustomer;
begin
  LCustomer := CreateCustomer;
  try
    FEntityMap.AddOrReplace(LCustomer);
    CheckTrue(FEntityMap.IsMapped(LCustomer));
    FEntityMap.Remove(LCustomer);
    CheckFalse(FEntityMap.IsMapped(LCustomer));
  finally
    LCustomer.Free;
  end;
end;

procedure TEntityMapTest.When_Changed_One_Property_GetChangedMembers_Returns_It;
var
  LCustomer: TCustomer;
  LChangedColumns: IList<ColumnAttribute>;
begin
  LCustomer := CreateCustomer;
  FEntityMap.AddOrReplace(LCustomer);
  LCustomer.Name := 'Changed';
  LChangedColumns := FEntityMap.GetChangedMembers(LCustomer, TEntityCache.Get(LCustomer.ClassType));
  CheckEquals(1, LChangedColumns.Count);
  CheckEquals('Name', LChangedColumns.First.Member.Name);
  LCustomer.Free;
end;

procedure TEntityMapTest.When_Replaced_Entity_No_MemoryLeaks;
var
  company: TCompany;
begin
  company := TCompany.Create;
  try
    FEntityMap.AddOrReplace(company);
    company.Logo.LoadFromFile(PictureFilename);
    FEntityMap.AddOrReplace(company);
  finally
    company.Free;
  end;
  Pass;
  SetFailsOnMemoryLeak(True);
end;

procedure TEntityMapTest.TestClear;
var
  LCustomer: TCustomer;
begin
  LCustomer := CreateCustomer;
  try
    FEntityMap.AddOrReplace(LCustomer);
    CheckTrue(FEntityMap.IsMapped(LCustomer));
    FEntityMap.Clear;
    CheckFalse(FEntityMap.IsMapped(LCustomer));
  finally
    LCustomer.Free;
  end;
end;

{$HINTS ON}

initialization
  RegisterTest('Spring.Persistence.Core', TEntityMapTest.Suite);

end.
